There is a continuing need to improve the security of computer applications and operating systems. More specifically, buffer overflow attacks remain a material threat to computer systems in view of the personal information theft and computer service disruption which occur when buffer overflow vulnerabilities are exploited.
Buffer overflow vulnerabilities can occur in an operating system or an application, whether on a client or server, or even in hardware such as in network devices. These vulnerabilities are often based on C/C++ programming languages or on components which have been derived or written in C/C++ languages, but in principle may exist in any software or hardware system having the essential characteristics giving rise to the vulnerability.
In general, a buffer overflow vulnerability exists when it is possible for a process or an application to write more data in a buffer than the buffer was designed to hold. If the process or application does not limit the data write to the predefined buffer address space, the excess data is written into other memory segments thereby overwriting and corrupting data saved into those segments. The excess data may include malicious code written in the machine language executable by the CPU, and may be configured to produce a shell prompt or other function according to the attacker's intentions. Where the buffer is a call stack buffer, the vulnerability may enable the attacker to disrupt or hijack application flow and inject and execute malicious code.
FIGS. 1A-1C illustrate a basic stack overflow attack. A known call stack 100 is shown in FIG. 1A having a call stack frame 103 for a function. The stack frame 103 has a parameters segment 110, a return address segment 120, a caller extender base pointer (EBP) segment 130, and a local variables segment 140. As shown, the stack grows downwardly toward lower memory addresses, while strings grow upwardly toward higher memory addresses, as is the case in the x86 family of instruction set architectures. Unless the associated function was called from a shell, a stack frame corresponding to the calling parent function will have been placed above this child function's frame on the stack, as indicated.
As is known, the parameters segment 110 stores parameters passed from the caller. A pointer to the branch point in memory of the execution code of a parent function following the call to the child function is stored in the return address segment 120. The EBP of the parent function's stack frame (pointing to the local variables in the parent function's stack frame), is stored in the caller EBP segment 130. The stack-frame base pointer then permits easy access to data structures passed on the stack, to the return instruction pointer, and to local variables added to the stack by the called procedure. Finally, local variables of the function are stored in the local variables segment 140.
As shown in FIG. 1B, the function may have been defined with a local variable char c[10], and thus on execution a corresponding memory space would be allocated in the local variables segment. If, as is the case with C/C++, the programming language does not automatically constrain data writes to the local variable address to the allocated memory space, and more data is written to the local variable address than the allocated space, the excess data will be written to adjacent segments.
Thus, as shown in FIG. 10, if the C/C++ strcpy function, which does not check the bounds of the string being copied, is used to write a string consisting of 15 ‘A’s followed by “newaddress”, for example, to the c[10] stack space, it will fill the local variables segment and overwrite both the called EBP and return address segments. By this method, an attacker may replace the stored return address of the parent function with a new address (indicated figuratively as “newaddress” in this example), and redirect execution of the process upon termination of the child function to malicious code stored elsewhere.
Variants of stack buffer overflow attacks exist including where the attack code is placed below the parameters segment thereby overwriting portions of the parent function stack frame.
Stack buffer overflow attacks of the sorts described above are well-known, and include examples such as the Slammer worm which caused considerable disruption and cost. Efforts have been made, therefore, to develop techniques to address and prevent stack buffer overflow attacks. Some solutions are based on hardware and include chipsets which detect and prevent stack buffer overrun attacks. (See, for example, Dailey Paulson, L., “New Chips Stop Buffer Overflow Attacks”, Computer (IEEE, 2004), p. 28.)
Other methods are software-based and employ various techniques for detecting and preventing stack buffer overflow bugs. For example, StackGuard™ is a technology that is used as an extension to the GCC compiler to detect and prevent buffer overrun vulnerabilities. (See Cowan, C. et al, “Stackguard: Automatic adaptive detection and preventions of buffer-overflow attacks”, Proc. of the USENIX Security Symposium, January 1998.) In this solution, a stored return address pointer of a function is protected by adding a value—a “canary”—onto the stack in front or behind the return address, depending on the respective directions of growth of the stack and of strings stored therein. The canary value is copied to another memory location. The copy is compared with the canary on the stack at the time of the function's epilogue. If the canary and the stored copy are not equal, then a buffer overflow condition is identified. If not, then the stack is considered to be intact.
In another solution, Microsoft provides in its C/C++ compiler an option engaged by the flag/GS for protection of the call stack. The protection provided is similar to StackGuard, described above, but adds an additional protection to the frame pointer or old base pointer which was not provided in StackGuard.
In general, both technologies rely on placing canaries between buffers and other sensitive data on the stack so that when an overflow occurs these values will be overwritten indicating a buffer overflow condition. Such methods are sufficient to protect against unintentional buffer overflows as it is highly improbable that any unintentional overwrite of the canary memory space will preserve the value of the canary. Where a buffer overflow vulnerability is being intentionally exploited, however, it remains possible to circumvent these measures by detecting the presence of the canary and configuring the buffer overflow write so as preserve the canary value while nevertheless overwriting the return address.
Another solution is taught by Conover et al. in U.S. Pat. No. 7,272,748. In this solution, a prologue of a function is hooked and a copy of the first state of a stack frame is saved. The function is allowed to complete. An epilogue of the function is hooked in a second state of the stack frame. The first state and second state of the stack frame are compared and a determination is made whether the stack frame is corrupted based on the comparison. If so, the second state of the stack frame is replaced with the saved first state and the function epilogue is allowed to complete. Conover et al. teaches, however, that the stack frame first state is saved to the heap. This method therefore requires special memory management, and thus Conover et al. further teaches the use of a hash table to address the saved stack frame first state on the heap. Moreover, Conover et al. teaches that a copy of the entire stack frame apart from the local variables buffer is desirable.
Another solution is taught by White in U.S. Pat. No. 7,581,089. In this solution, a second stack is created to store shadow frames containing the return address on a first, normal stack. The second stack is different from the first stack, and has a second stack pointer. The second stack also contains the address on the first stack containing the return address and a user-definable state variable which is used to identify a shadow frame as a return address. Before returning from a subroutine, the two return addresses are compared and if they do not match the second stack is searched down and then up from a matching return address. If there is a match, the shadow is re-synchronized with the first stack by comparing the stored values of the first stack pointer with the first stack pointer and adjusting appropriately the shadow stack pointer. This method addresses only the value of the return pointer on the first stack, however. The method is further vulnerable to the intentional placement of a known return address in a function stack frame different from a present function stack frame for redirection to shell code since the method searches the second stack up and down for a matching address. Unrestricted string writes spanning multiple stack frames therefore remains a feasible buffer vulnerability exploit.
There remains a need, therefore, for improved solutions to protect against stack buffer overflow exploits.